﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Net.Nntp.Cache
{
    using System;
    using System.Collections.Generic;
    using System.Data.SqlServerCe;
    using System.Globalization;
    using System.IO;

    /// <summary>See <see cref="IInboundArticle"/>.</summary>
    internal sealed class InboundArticle : IInboundArticle
    {
        private const int MaxFilesPerDirectory = 128;

        private readonly Group group;
        private readonly long number;
        private readonly DateTime? dateUtc;
        private readonly string from;
        private readonly string subject;
        private readonly ReceiveStatus receiveStatus;
        private bool? hasAttachments;
        private bool isBeingWatched;
        private bool isExpanded;
        private bool hasBeenRead;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IInboundArticle.Number"/>.</summary>
        public long Number
        {
            get { return this.number; }
        }

        /// <summary>See <see cref="IInboundArticle.DateUtc"/>.</summary>
        public DateTime? DateUtc
        {
            get { return this.dateUtc; }
        }

        /// <summary>See <see cref="IInboundArticle.From"/>.</summary>
        public string From
        {
            get { return this.from; }
        }

        /// <summary>See <see cref="IInboundArticle.Subject"/>.</summary>
        public string Subject
        {
            get { return this.subject; }
        }

        /// <summary>See <see cref="IInboundArticle.ReceiveStatus"/>.</summary>
        public ReceiveStatus ReceiveStatus
        {
            get { return this.receiveStatus; }
        }

        /// <summary>See <see cref="IInboundArticle.HasAttachments"/>.</summary>
        public bool? HasAttachments
        {
            get
            {
                return this.hasAttachments;
            }

            set
            {
                this.hasAttachments = value;
                this.group.Host.Database.InboundArticleUpdater.UpdateHasAttachments(this);
            }
        }

        /// <summary>See <see cref="IInboundArticle.IsBeingWatched"/>.</summary>
        public bool IsBeingWatched
        {
            get
            {
                return this.isBeingWatched;
            }

            set
            {
                this.isBeingWatched = value;
                this.group.Host.Database.InboundArticleUpdater.UpdateIsBeingWatched(this);
            }
        }

        /// <summary>See <see cref="IInboundArticle.IsExpanded"/>.</summary>
        public bool IsExpanded
        {
            get
            {
                return this.isExpanded;
            }
            
            set
            {
                this.isExpanded = value;
                this.group.Host.Database.InboundArticleUpdater.UpdateIsExpanded(this);
            }
        }

        /// <summary>See <see cref="IInboundArticle.HasBeenRead"/>.</summary>
        public bool HasBeenRead
        {
            get
            {
                return this.hasBeenRead;
            }

            set
            {
                this.hasBeenRead = value;
                this.group.Host.Database.InboundArticleUpdater.UpdateHasBeenRead(this);
            }
        }

        /// <summary>See <see cref="IInboundArticle.IsSelected"/>.</summary>
        public bool IsSelected
        {
            get { return this.number == this.group.SelectedInboundArticleNumber; }
            set { this.group.SelectedInboundArticleNumber = this.number; }
        }

        /// <summary>See <see cref="IInboundArticle.GetContent"/>.</summary>
        public Stream GetContent()
        {
            if (this.receiveStatus == ReceiveStatus.OverviewOnly)
            {
                throw new InvalidOperationException("No article content has been received.");
            }

            return new FileStream(
                GetDataFilePath(this.group.DataDirectory, this.number),
                FileMode.Open,
                FileAccess.Read,
                FileShare.ReadWrite); // We allow read-write to prevent disruption of simultaneous body receive
        }

        /// <summary>See <see cref="IInboundArticle.GetFollowupInboundArticles"/>.</summary>
        public IEnumerable<IInboundArticle> GetFollowupInboundArticles()
        {
            return InboundArticleSelector.GetArticles(this.group, this.number);
        }

        /// <summary>See <see cref="IEquatable{T}.Equals"/>.</summary>
        public bool Equals(IInboundArticle other)
        {
            return this.Equals(other as InboundArticle);
        }

        /// <summary>See <see cref="object.Equals(object)"/>.</summary>
        public sealed override bool Equals(object obj)
        {
            return this.Equals(obj as InboundArticle);
        }

        /// <summary>See <see cref="object.GetHashCode"/>.</summary>
        public sealed override int GetHashCode()
        {
            return this.number.GetHashCode() ^ this.group.Id;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal InboundArticle(Group group, SqlCeDataReader reader)
        {
            this.group = group;

            this.number = (long)reader[InboundArticleNames.NumberColumn];
            this.dateUtc = reader.GetNullableColumn<DateTime?>(InboundArticleNames.DateUtcColumn);
            this.from = reader.GetNullableColumn<string>(InboundArticleNames.FromColumn);
            this.subject = reader.GetNullableColumn<string>(InboundArticleNames.SubjectColumn);

            if ((bool)reader[InboundArticleNames.HasBodyBeenReceivedColumn])
            {
                this.receiveStatus = ReceiveStatus.FullArticle;
            }
            else if ((bool)reader[InboundArticleNames.HasHeaderBeenReceivedColumn])
            {
                this.receiveStatus = ReceiveStatus.HeaderOnly;
            }
            else
            {
                this.receiveStatus = ReceiveStatus.OverviewOnly;
            }

            this.hasAttachments = reader.GetNullableColumn<bool?>(InboundArticleNames.HasAttachmentsColumn);
            this.isBeingWatched = (bool)reader[InboundArticleNames.IsBeingWatchedColumn];
            this.isExpanded = (bool)reader[InboundArticleNames.IsExpandedColumn];
            this.hasBeenRead = (bool)reader[InboundArticleNames.HasBeenReadColumn];
        }

        internal Group Group
        {
            get { return this.group; }
        }

        internal static string GetDataFilePath(string groupDataDirectory, long number)
        {
            var subdir = Path.Combine(
                groupDataDirectory,
                (number / MaxFilesPerDirectory * MaxFilesPerDirectory).ToString(CultureInfo.InvariantCulture));
            return Path.Combine(subdir, number.ToString(CultureInfo.InvariantCulture) + ".eml");
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IInboundArticle.Group"/>.</summary>
        IGroup IInboundArticle.Group
        {
            get { return this.Group; }
        }

        private bool Equals(InboundArticle other)
        {
            return (other != null) && (other.number == this.number) && (other.group.Id == this.group.Id);
        }
    }
}
